<?php

namespace App\Console\Commands;

use Facebook\WebDriver\Chrome\ChromeDriver;
use Facebook\WebDriver\Chrome\ChromeOptions;
use Facebook\WebDriver\Cookie;
use Facebook\WebDriver\Remote\DesiredCapabilities;
use Facebook\WebDriver\WebDriverExpectedCondition;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
use Illuminate\Console\Command;
use Psr\Http\Message\ResponseInterface;

// https://blog.csdn.net/newnewfeng/article/details/52275650
// https://cloud.tencent.com/document/product/266
class LagouCourseDl extends Command
{
    /**
     * @var string $ua
     */
    private $ua;

    /**
     * @var Cookie[] $cookies
     */
    private $cookies;

    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'lagou:dl {url? : The course url}';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Download lagou course';

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
        $start = time();
        $url = $this->argument('url') ?? 'https://kaiwu.lagou.com/course/courseInfo.htm?courseId=1&sid=3';
        $split = mb_split('kaiwu.lagou.com/course/', $url);
        if (2 != count($split)) {
            $this->error('url error');
            return;
        }

        $this->info("\n^_^ It may take a while, please be patient. ^_^\n");
        $driver = $this->initChrome();
        try {
            $driver->get($url);
            $courseInfo = $driver->executeScript('return JSON.stringify(courseInfo);');
            if ($courseInfo) {
                app('log')->info('courseInfo', compact('url', 'courseInfo'));
            }
            $decoded = json_decode($courseInfo, true);
            $courseLessons = $decoded['courseSections'][0]['courseLessons'];
            $courseName = $decoded['courseName'];
            foreach ($courseLessons as $courseLesson) {
                $appId = $courseLesson['appId'];
                $fileId = $courseLesson['fileId'];
                $lessonName = $courseName . '/' . $courseLesson['id'] . '. ' . $courseLesson['theme'];
                $lastTranscode = $this->getPlayInfo($appId, $fileId);

                if ($lastTranscode) {
                    $m3u8Url = $lastTranscode['url'];
                    $urlInfo = "lagou/$lessonName/url"; // url
                    $m3u8Filename = "lagou/$lessonName/" . basename($m3u8Url); // m3u8
                    $keyInfo = "lagou/$lessonName/key"; // key
                    $m3u8Content = $this->getFileContent($m3u8Url);
                    $keyUri = $this->getKeyUri($m3u8Content);
                    $this->info($keyUri);
                    if (!$this->write2File($urlInfo, str_replace_last('m3u8', 'ts', $m3u8Url))
                        || !$this->write2File($keyInfo, $keyUri)
                        || !$this->write2File($m3u8Filename, $m3u8Content)) {
                        app('log')->error('on save', compact('lessonName', 'url'));
                    }
                }
            }
        } catch (\Exception $e) {
            app('log')->error($e);
            $this->error($e->getMessage());
        } finally {
            $driver->close();
            $cost = time() - $start;
            $this->info("--- DONE ---\ncost: $cost s");
        }
    }

    /**
     * Init and start the browser
     *
     * @return ChromeDriver
     */
    private function initChrome(): ChromeDriver
    {
        putenv('webdriver.chrome.driver=' . env('CHROME_DRIVER', shell_exec('which chromedriver')));
        $caps = DesiredCapabilities::chrome();
        $options = new ChromeOptions();
        $options->addArguments([
            '--window-size=1920,1080',
            '--disable-gpu',
            '--headless',
            '--no-sandbox',
            '--disable-dev-shm-usage',
            '--mute-audio',
        ]);
        $caps->setCapability(ChromeOptions::CAPABILITY, $options);
        return ChromeDriver::start($caps);
    }

    /**
     * Login user account
     *
     * @param $driver
     *
     * @return bool
     */
    private function login($driver): bool
    {
        $name = env('LAGOU_USERNAME');
        $pwd = env('LAGOU_PASSWORD');
        if (!$name || !$pwd) {
            $this->error('username or password not set');
            return false;
        }
        $url = 'https://passport.lagou.com/login/login.html';
        $driver->get($url);
        usleep(1000000); // 1 s
        $submitScript = "
(() => {
    document.querySelector('form input[name=username]').value = '$name';
    document.querySelector('form input[name=password]').value = '$pwd';
    document.querySelector('form input[type=submit]').click();
})();";
        $driver->executeScript($submitScript);
        // geetest
        $driver->wait(5, 250)
            ->until(WebDriverExpectedCondition::not(
                WebDriverExpectedCondition::urlIs($url)), 'login timeout');
        $this->cookies = $driver->manage()->getCookies();

        return true;
    }

    private function getPlayInfo($appId, $fileId): array
    {
        $lastTranscode = null;
        if (!$appId || !$fileId) {
            return $lastTranscode;
        }
        $url = "https://playvideo.qcloud.com/getplayinfo/v2/$appId/$fileId";

        $client = new Client([
            'base_uri' => $url,
        ]);
        $client->requestAsync('get')
            ->then(function (ResponseInterface $res) use (&$lastTranscode) {
                if (200 == $res->getStatusCode()) {
                    $contents = $res->getBody()->getContents();
                    $transcodeList = json_decode($contents, true)['videoInfo']['transcodeList'];
                    $lastTranscode = end($transcodeList);
                }
            }, function (RequestException $e) {
                app('log')->error($e);
            })->wait();
        return $lastTranscode;
    }

    private function getFileContent($url): string
    {
        $file = '';
        if (!$url) {
            return $file;
        }
        $client = new Client([
            'base_uri' => $url,
        ]);
        $client->requestAsync('get')->then(function (ResponseInterface $res) use (&$file) {
            if (200 == $res->getStatusCode()) {
                $file = $res->getBody()->getContents();
            }
        }, function (RequestException $e) {
            app('log')->error($e);
        })->wait();
        return $file;
    }

    private function getKeyUri($m3u8Content): string
    {
        $after = 'URI="';
        $before = '",IV=0x';
        $a = strpos($m3u8Content, $after);
        $b = strpos($m3u8Content, $before);
        $uriLen = $b - $a - strlen($after);
        return substr($m3u8Content, $a + strlen($after), $uriLen);
    }

    /**
     * @param string $filename with path
     * @param string $contents The file contents.
     *
     * @return bool
     */
    private function write2File($filename, $contents): bool
    {
        $b = false;
        try {
            $b = app('filesystem')->disk('local')->write($filename, $contents);
        } catch (\Exception $e) {
            app('log')->info('write2file failed', ['msg' => $e->getMessage(), 'code' => $e->getCode()]);
        }
        return $b;
    }
}
